package com.member.server.domain.dto;

import com.member.server.entity.*;
import com.member.server.enums.RecordStatus;
import com.member.server.enums.RecordType;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author gaojx1993
 * @version 1.0
 * @description AccRefundDTO
 * @since 2023/7/12 7:43
 */
public class AccRefundDTO extends AccOpDTO {

    private final Long refundAmount;

    private final List<Account> accounts;

    private final Record payRecord;

    private final List<RecordAccountPay> payDetails;

    private final List<RecordAccountRefund> alreadyRefundRecords;

    private Long availableRefundAmount;

    private Map<Long, Long> availableRefundInfo;

    private Map<Long, Long> refundInfo;

    private List<RecordAccountRefund> recordAccountRefunds;

    public AccRefundDTO(Member member, Long refundAmount, List<Account> accounts, Record payRecord,
                        List<RecordAccountPay> payDetails, List<RecordAccountRefund> alreadyRefundRecords) {
        super(member);
        this.refundAmount = refundAmount;
        this.accounts = accounts;
        this.payRecord = payRecord;
        this.payDetails = payDetails;
        this.alreadyRefundRecords = alreadyRefundRecords;
        init();
    }

    @Override
    public void init() {
        // 生成可退款金额信息
        genAvailableRefundInfo();
        // 生成退款金额信息
        genRefundInfo();
        // 生成退款流水
        genRecord();
        // 生成退款流水详情
        genAccountRefunds();
        // 生成账户余额变动记录
        genAccountLogs();
    }

    @Override
    public void genRecord() {
        record = new Record();
        record.setGmtCreate(gmtCreate);
        record.setGmtUpdate(gmtCreate);
        record.setMemberId(payRecord.getMemberId());
        record.setMemberName(payRecord.getMemberName());
        record.setType(RecordType.refund.getCode());
        record.setStatus(RecordStatus.finished.getCode());
        record.setTotalAmount(refundAmount);
        record.setRealAmount(refundAmount);
        record.setGmtFinish(gmtCreate);
        record.setRemark(recordRemark);
    }

    @Override
    public void genAccountLogs() {
        this.accountLogs = new ArrayList<>();
        for (Map.Entry<Long, Long> entry : refundInfo.entrySet()) {
            Long accountId = entry.getKey();
            Optional<Account> optional = accounts.stream().filter(a -> Objects.equals(accountId, a.getId())).findAny();
            if (optional.isPresent()) {
                Account account = optional.get();
                AccountLog accountLog = new AccountLog();
                accountLog.setGmtCreate(gmtCreate);
                accountLog.setGmtUpdate(gmtCreate);
                accountLog.setAccountId(accountId);
                accountLog.setBeforeBalance(account.getBalance());
                accountLog.setAfterBalance(account.getBalance() + entry.getValue());
                accountLogs.add(accountLog);
            }
        }
    }

    public Long getRefundAmount() {
        return refundAmount;
    }

    public List<Account> getAccounts() {
        return accounts;
    }

    public Record getPayRecord() {
        return payRecord;
    }

    public List<RecordAccountPay> getPayDetails() {
        return payDetails;
    }

    public List<RecordAccountRefund> getAlreadyRefundRecords() {
        return alreadyRefundRecords;
    }

    public Map<Long, Long> getAvailableRefundInfo() {
        return availableRefundInfo;
    }

    public List<RecordAccountRefund> getRecordAccountRefunds() {
        return recordAccountRefunds;
    }

    private void genAvailableRefundInfo() {
        availableRefundInfo = new HashMap<>(payDetails.size());
        for (RecordAccountPay payDetail : payDetails) {
            Long accountId = payDetail.getAccountId();
            Long realAmount = payDetail.getRealAmount();
            long refunded = alreadyRefundRecords.stream().filter(re -> Objects.equals(re.getAccountId(), accountId))
                    .mapToLong(RecordAccountRefund::getRefundAmount).sum();
            if (refunded < realAmount) {
                availableRefundInfo.put(accountId, realAmount - refunded);
            }
        }
        availableRefundAmount = availableRefundInfo.values().stream().mapToLong(l -> l).sum();
        if (availableRefundAmount == 0) {
            throw new RuntimeException("已无可退金额！");
        }
        if (refundAmount > availableRefundAmount) {
            throw new RuntimeException("退款金额超出可退金额！");
        }
    }

    private void genRefundInfo() {
        Map<Long, Long> sortedMap = new LinkedHashMap<>();
        List<Map.Entry<Long, Long>> list = availableRefundInfo.entrySet().stream().sorted(Map.Entry.comparingByValue()).collect(Collectors.toList());
        // 可退金额从大到小排序
        Collections.reverse(list);
        list.forEach(e -> sortedMap.put(e.getKey(), e.getValue()));

        refundInfo = new LinkedHashMap<>();
        long currRefund = refundAmount;
        for (Map.Entry<Long, Long> entry : sortedMap.entrySet()) {
            long amount = Math.min(currRefund, entry.getValue());
            refundInfo.put(entry.getKey(), amount);
            currRefund = currRefund - amount;
            if (currRefund <= 0) {
                break;
            }
        }
    }

    private void genAccountRefunds() {
        recordAccountRefunds = new ArrayList<>(refundInfo.size());
        for (Map.Entry<Long, Long> entry : refundInfo.entrySet()) {
            Optional<RecordAccountPay> any = payDetails.stream().filter(ap -> Objects.equals(ap.getAccountId(), entry.getKey())).findAny();
            if (any.isPresent()) {
                RecordAccountPay recordAccountPay = any.get();
                RecordAccountRefund recordAccountRefund = new RecordAccountRefund();
                recordAccountRefund.setGmtCreate(gmtCreate);
                recordAccountRefund.setGmtUpdate(gmtCreate);
                recordAccountRefund.setAccountId(entry.getKey());
                recordAccountRefund.setRefundAmount(entry.getValue());
                recordAccountRefund.setPayRecordId(payRecord.getId());
                recordAccountRefund.setPayAmount(recordAccountPay.getRealAmount());
                recordAccountRefunds.add(recordAccountRefund);
            }
        }
    }
}
